package com.example.cloud.socket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.cloud.dao.mongdb.system.MongoUserRepository;
import com.example.cloud.encoder.ServerEncoder;
import com.example.cloud.model.system.Organ;
import com.example.cloud.model.system.User;
import com.example.cloud.pojo.Message;
import com.example.cloud.pojo.MineMessage;
import com.example.cloud.pojo.ResponseMessage;
import com.example.cloud.pojo.ToMessage;
import com.example.cloud.service.system.UserService;
import com.example.cloud.utils.RequestUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Created by wangtao on 2018/1/19.
 */
@ServerEndpoint(value = "/websocket",encoders = {ServerEncoder.class})
@Component
public class WebSocketServer {

    private static ApplicationContext applicationContext;

    private UserService userService;

    private MongoUserRepository mongoUserRepository;

    public static void setApplicationContext(ApplicationContext applicationContext) {
        WebSocketServer.applicationContext = applicationContext;
    }

    public void setMongoUserRepository(MongoUserRepository mongoUserRepository) {
        this.mongoUserRepository = mongoUserRepository;
    }

    //静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;

    //concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();

    public static Map<Long, WebSocketServer> clientMaps = new LinkedHashMap<Long, WebSocketServer>();

    //与某个客户端的连接会话，需要通过它来给客户端发送数据
    private Session session;
    //用于识别客户端连接身份
    private Long clientId;

    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session) {
        if(this.mongoUserRepository == null){
            mongoUserRepository = applicationContext.getBean(MongoUserRepository.class);
        }
        this.session = session;
        if(session.getRequestParameterMap().containsKey("clientId") && session.getRequestParameterMap().get("clientId") != null){
            clientId = Long.valueOf(session.getRequestParameterMap().get("clientId").get(0));
        }
        if(!clientMaps.containsKey(clientId)){
            clientMaps.put(clientId, this);
            addOnlineCount();
            System.out.println("有一连接加入！当前在线人数为" + getOnlineCount());
            // 通知其他在线客户端更新列表
            try{
                JSONObject result = new JSONObject();
                result.put("emit", "refresh");
                result.put("id", clientId);
                result.put("status", "online");
                for (WebSocketServer socket : clientMaps.values()) {
                    socket.sendMessage(JSON.toJSONString(result));
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if(this.mongoUserRepository == null){
            mongoUserRepository = applicationContext.getBean(MongoUserRepository.class);
        }
        if(clientMaps.containsKey(clientId)){
            clientMaps.remove(clientId);
            subOnlineCount();
            System.out.println("有一连接关闭！当前在线人数为" + getOnlineCount());
            // 通知其他在线客户端更新列表
            try{
                JSONObject result = new JSONObject();
                result.put("emit", "refresh");
                result.put("id", clientId);
                result.put("status", "offline");
                for (WebSocketServer socket : clientMaps.values()) {
                    socket.sendMessage(JSON.toJSONString(result));
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        if(this.mongoUserRepository == null){
            mongoUserRepository = applicationContext.getBean(MongoUserRepository.class);
        }
        JSONObject result = new JSONObject();
        JSONObject json = (JSONObject)JSON.parse(message);
        String type = json.getString("type");
        JSONObject data = (JSONObject)JSON.parse(json.getString("data"));
        MineMessage mine = JSON.parseObject(data.getString("mine"), MineMessage.class);
        ToMessage to = JSON.parseObject(data.getString("to"), ToMessage.class);
        ResponseMessage responseMessage = new ResponseMessage();
        String toType = to.getType();
        if("friend".equals(toType)){
            responseMessage.setId(mine.getId());
        }else if("group".equals(toType)){
            responseMessage.setId(to.getId());
            responseMessage.setFromid(mine.getId());
        }
        responseMessage.setUsername(mine.getUsername());
        responseMessage.setAvatar(mine.getAvatar());
        responseMessage.setContent(mine.getContent());
        responseMessage.setMine(false);
        responseMessage.setTimestamp(System.currentTimeMillis());
        responseMessage.setType(to.getType());
        result.put("emit", type);
        result.put("data", responseMessage);
        // 好友消息
        if("friend".equals(toType)){
            if(clientMaps.containsKey(to.getId())){
                try {
                    clientMaps.get(to.getId()).sendMessage(JSON.toJSONString(result));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        // 群组消息
        }else if("group".equals(toType)){
            // 获取群组人员列表
            List<User> users = mongoUserRepository.findByIdIn(clientMaps.keySet());
            for (User user : users) {
                // 1.不发送给自己 2.再次判断客户端是否在线
                if(!mine.getId().equals(user.getId()) && clientMaps.containsKey(user.getId())){
                    // 判断当前用户是否在该群组中
                    List<Long> organIds = new ArrayList<>();
                    for (Organ organ : user.getOrgans()) {
                        organIds.add(organ.getId());
                    }
                    if(organIds.contains(to.getId())){
                        clientMaps.get(user.getId()).sendMessage(JSON.toJSONString(result));
                    }
                }
            }
        }
    }

    /**
     * 发生错误时调用
     @OnError
     */
     public void onError(Session session, Throwable error) {
         error.printStackTrace();
     }


     public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
     }

    public void sendObject(Object object) throws IOException, EncodeException {
        this.session.getBasicRemote().sendObject(object);
    }


     /**
      * 群发自定义消息
      * */
    public static void sendInfo(String message, List<Long> clientIds, int type) throws IOException, EncodeException {
        for (Long clientId : clientIds) {
            // 判断客户端是否在线，在线则推送消息
            if(clientMaps.containsKey(clientId)){
                clientMaps.get(clientId).sendObject(new Message(RequestUtil.getLoginUser().getUsername(), message, type, null));
            }
        }
    }

    /**
     * IM消息
     * @param clientIds
     * @throws IOException
     * @throws EncodeException
     */
    public static void send(JSONObject json, List<Long> clientIds) throws IOException, EncodeException {
        for (Long clientId : clientIds) {
            // 判断客户端是否在线，在线则推送消息
            if(clientMaps.containsKey(clientId)){
                clientMaps.get(clientId).sendMessage(JSON.toJSONString(json));
            }
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        if(onlineCount > 0){
            onlineCount--;
        };
    }
}
